/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.configuration;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.sourceprojects.lycia.LyciaConfigurator;
import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.QualifiedName;
import org.sourceprojects.lycia.TagDefinitionParser;
import org.sourceprojects.lycia.annotations.Tag;
import org.w3c.dom.Element;

/**
 * 
 * @author noctarius
 * @since 1.9.1
 * 
 * @param <B>
 */
public class RankingXmlParserConfigurator<B> extends
		ValidationAwareXmlParserConfigurator<B> {

	private static final int RANKING_VALUE_DEFAULT_PARSER = 5;
	private static final int RANKING_VALUE_TAGGED_PARSER = 10;
	private static final int RANKING_VALUE_SPECIAL_PARAMETER_TYPE = 15;
	private static final int RANKING_VALUE_ANNOTATED_PARAMETER_TYPE = 20;

	private final RankComparator<B> rankComparator = new RankComparator<B>();

	public RankingXmlParserConfigurator(final LyciaParser<B> xmlParser,
			final LyciaConfigurator<B> parent) {
		super(xmlParser, parent);
	}

	@Override
	public TagDefinitionParser<B> findTagDefinitionParser(
			final QualifiedName localElement, final Element element) {
		final List<TagDefinitionParser<B>> parsers = findTagDefinitionParsers(
				localElement.getLocalPart(), localElement.getNamespaceURI(),
				element);

		if (parsers.size() == 0) {
			return null;
		}

		final List<RankingContainer<B>> ranking = rankTagDefinitionParsers(
				parsers, localElement, element);

		return ranking.get(0).getParser();
	}

	@Override
	public List<TagDefinitionParser<B>> findTagDefinitionParsers(
			final QualifiedName localElement, final Element element) {
		List<TagDefinitionParser<B>> parsers = super.findTagDefinitionParsers(
				localElement, element);

		final List<RankingContainer<B>> ranking = rankTagDefinitionParsers(
				parsers, localElement, element);

		parsers = new ArrayList<TagDefinitionParser<B>>();
		for (final RankingContainer<B> rank : ranking) {
			parsers.add(rank.getParser());
		}

		return parsers;
	}

	private List<RankingContainer<B>> rankTagDefinitionParsers(
			final List<TagDefinitionParser<B>> parsers,
			final QualifiedName localElement, final Element element) {
		final List<RankingContainer<B>> ranking = new ArrayList<RankingContainer<B>>();

		for (final TagDefinitionParser<B> parser : parsers) {
			final RankingContainer<B> container = new RankingContainer<B>(
					parser);

			for (final Tag tag : parser.getTagAnnotations()) {
				container.addRanking(calculatePoints(tag, localElement
						.getLocalPart(), localElement.getNamespaceURI(),
						element));
			}

			ranking.add(container);
		}

		// TODO: do some ranking

		Collections.sort(ranking, rankComparator);
		return ranking;
	}

	private int calculatePoints(final Tag tag, final String tagname,
			final String namespaceURI, Element element) {
		final String expression = tag.value();
		final String namespace = "".equals(tag.namespace()) ? namespaceURI
				: tag.namespace();

		if (!TagConfigurationUtils.isNullXPathAnnotation(tag)) {
			return RANKING_VALUE_ANNOTATED_PARAMETER_TYPE;
		}

		if (expression.contains("\\")) {
			final QualifiedName[] qNames = generateParentQNames(expression,
					namespace);
			boolean found = true;

			for (int i = qNames.length - 1; i > -1; i--) {
				final QualifiedName qName = qNames[i];
				final String localPart = qName.getLocalPart();
				final String namespacePart = qName.getNamespaceURI();

				if (!localPart.equals(element.getLocalName())
						|| !namespacePart.equals(element.getNamespaceURI())) {
					found = false;
					break;
				}

				if (i > 0) {
					if (element.getParentNode() == null) {
						found = false;
						break;
					} else {
						element = (Element) element.getParentNode();
					}
				}
			}

			if (found) {
				return RANKING_VALUE_SPECIAL_PARAMETER_TYPE;
			}
		} else if (expression.equals(tagname) && namespace.equals(namespaceURI)) {
			return RANKING_VALUE_TAGGED_PARSER;
		} else if (expression.equals("*") && namespace.equals(namespaceURI)) {
			return RANKING_VALUE_DEFAULT_PARSER;
		}

		return 0;
	}

	private static class RankingContainer<B> {
		private final TagDefinitionParser<B> parser;
		private int ranking = 0;

		public RankingContainer(final TagDefinitionParser<B> parser) {
			this.parser = parser;
		}

		public TagDefinitionParser<B> getParser() {
			return parser;
		}

		public void addRanking(final int value) {
			ranking += value;
		}

		public int getRanking() {
			return ranking;
		}
	}

	private static class RankComparator<B> implements
			Comparator<RankingContainer<B>>, Serializable {
		private static final long serialVersionUID = 1L;

		@Override
		public int compare(final RankingContainer<B> o1,
				final RankingContainer<B> o2) {
			if (o1.getRanking() == o2.getRanking()) {
				return 0;
			}

			return o1.getRanking() > o2.getRanking() ? -1 : 1;
		}
	}
}
